Skip to content

Method: cloneCollectionElement(Object, Field, Object, CloneConfiguration)

1: /**
2: * Copyright (C) 2023 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.sessions;
16:
17: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
19: import cz.cvut.kbss.jopa.model.annotations.Types;
20: import cz.cvut.kbss.jopa.model.metamodel.CollectionType;
21: import cz.cvut.kbss.jopa.utils.CollectionFactory;
22: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
23: import cz.cvut.kbss.jopa.utils.MetamodelUtils;
24: import org.slf4j.Logger;
25: import org.slf4j.LoggerFactory;
26:
27: import java.lang.reflect.Constructor;
28: import java.lang.reflect.Field;
29: import java.lang.reflect.InvocationTargetException;
30: import java.security.AccessController;
31: import java.security.PrivilegedActionException;
32: import java.util.*;
33:
34: /**
35: * Special class for cloning collections. Introduced because some Java collection have no no-argument constructor and
36: * thus they must be cloned specially. NOTE: This class may be removed in case a better cloning mechanisms (namely
37: * database mappings and copy policies) is introduced.
38: */
39: class CollectionInstanceBuilder extends AbstractInstanceBuilder {
40:
41: private static final Logger LOG = LoggerFactory.getLogger(CollectionInstanceBuilder.class);
42:
43: private static final Class<?> singletonListClass = Collections.singletonList(null).getClass();
44: private static final Class<?> singletonSetClass = Collections.singleton(null).getClass();
45: private static final Class<?> arrayAsListClass = Arrays.asList(null, null).getClass();
46:
47: CollectionInstanceBuilder(CloneBuilderImpl builder, UnitOfWorkImpl uow) {
48: super(builder, uow);
49: }
50:
51: /**
52: * This method is the entry point for cloning the Java collections. It clones standard collections as well as
53: * immutable collections and singleton collections.
54: * <p>
55: * Currently supported are List and Set.
56: *
57: * @param collection The collection to clone
58: * @return A deep clone of the specified collection
59: */
60: @Override
61: Object buildClone(Object cloneOwner, Field field, Object collection, CloneConfiguration configuration) {
62: assert collection instanceof Collection;
63: Collection<?> container = (Collection<?>) collection;
64: if (container instanceof IndirectCollection<?>) {
65: container = (Collection<?>) ((IndirectCollection<?>) container).unwrap();
66: }
67: if (Collections.emptyList() == container || Collections.emptySet() == container) {
68: return container;
69: }
70: Object clone = cloneUsingDefaultConstructor(cloneOwner, field, container, configuration);
71: if (clone == null) {
72: clone = buildInstanceOfSpecialCollection(cloneOwner, field, container, configuration);
73: }
74: if (clone == null) {
75: clone = buildDefaultCollectionInstance(cloneOwner, field, container, configuration);
76: }
77: clone = uow.createIndirectCollection(clone, cloneOwner, field);
78: return clone;
79: }
80:
81: /**
82: * Clones the specified collection using its default zero argument constructor. If the specified collection has none
83: * (e. g. like SingletonList), this method returns null.
84: *
85: * @param container The collection to clone.
86: * @return cloned collection
87: */
88: private Collection<?> cloneUsingDefaultConstructor(Object cloneOwner, Field field,
89: Collection<?> container, CloneConfiguration configuration) {
90: Class<?> javaClass = container.getClass();
91: final Optional<Collection<?>> result = createNewInstance(javaClass, container.size());
92: // Makes shallow copy
93: result.ifPresent(r -> cloneCollectionContent(cloneOwner, field, container, r, configuration));
94: return result.orElse(null);
95: }
96:
97: private static Optional<Collection<?>> createNewInstance(Class<?> type, int size) {
98: Object[] params = null;
99: Class<?>[] types = {int.class};
100: // Look for constructor taking initial size as parameter
101: Constructor<?> ctor = getDeclaredConstructorFor(type, types);
102: if (ctor != null) {
103: params = new Object[1];
104: params[0] = size;
105: } else {
106: ctor = DefaultInstanceBuilder.getDeclaredConstructorFor(type, null);
107: }
108: if (ctor == null) {
109: return Optional.empty();
110: }
111: Collection<?> result = null;
112: try {
113: result = (Collection<?>) ctor.newInstance(params);
114: } catch (InstantiationException | InvocationTargetException | IllegalArgumentException e) {
115: throw new OWLPersistenceException(e);
116: } catch (IllegalAccessException e) {
117: logConstructorAccessException(ctor, e);
118: try {
119: result = (Collection<?>) AccessController.doPrivileged(new PrivilegedInstanceCreator(ctor));
120: } catch (PrivilegedActionException ex) {
121: logPrivilegedConstructorAccessException(ctor, ex);
122: // Do nothing
123: }
124: }
125: return Optional.ofNullable(result);
126: }
127:
128: /**
129: * Clone all the elements in the collection. This will make sure that the cloning process creates a deep copy.
130: *
131: * @param source The collection to clone.
132: */
133: private void cloneCollectionContent(Object cloneOwner, Field field, Collection<?> source,
134: Collection<?> target, CloneConfiguration configuration) {
135: if (source.isEmpty()) {
136: return;
137: }
138: Collection<Object> tg = (Collection<Object>) target;
139: for (Object elem : source) {
140: if (elem == null) {
141: tg.add(null);
142: continue;
143: }
144: if (CloneBuilderImpl.isImmutable(elem)) {
145: tg.addAll(source);
146: break;
147: }
148: tg.add(cloneCollectionElement(cloneOwner, field, elem, configuration));
149: }
150: }
151:
152: private Object cloneCollectionElement(Object cloneOwner, Field field, Object element,
153: CloneConfiguration configuration) {
154: Object clone;
155:• if (builder.isTypeManaged(element.getClass())) {
156: clone = uow.registerExistingObject(element, configuration.getDescriptor(), configuration.getPostRegister());
157: } else {
158: clone = builder.buildClone(cloneOwner, field, element, configuration.getDescriptor());
159: }
160: return clone;
161: }
162:
163:
164: private Collection<?> buildInstanceOfSpecialCollection(Object cloneOwner, Field field, Collection<?> container,
165: CloneConfiguration configuration) {
166: if (arrayAsListClass.isInstance(container)) {
167: final List<?> arrayList = new ArrayList<>(container.size());
168: cloneCollectionContent(cloneOwner, field, container, arrayList, configuration);
169: return arrayList;
170: } else if (singletonListClass.isInstance(container) || singletonSetClass.isInstance(container)) {
171: final Object element = container.iterator().next();
172: final Object elementClone = CloneBuilderImpl.isImmutable(element) ? element :
173: cloneCollectionElement(cloneOwner, field, element, configuration);
174: return singletonListClass.isInstance(container) ? Collections.singletonList(elementClone) :
175: Collections.singleton(elementClone);
176: } else {
177: return null;
178: }
179: }
180:
181: private Collection<?> buildDefaultCollectionInstance(Object cloneOwner, Field field, Collection<?> container,
182: CloneConfiguration configuration) {
183: LOG.trace("Unable to find matching collection constructor. Creating default collection.");
184: final Collection<?> clone;
185: if (container instanceof List) {
186: clone = CollectionFactory.createDefaultCollection(CollectionType.LIST);
187: } else if (container instanceof Set) {
188: clone = CollectionFactory.createDefaultCollection(CollectionType.SET);
189: } else {
190: throw new OWLPersistenceException(
191: "Cannot clone unsupported collection instance of type " + container.getClass() + ".");
192: }
193: cloneCollectionContent(cloneOwner, field, container, clone, configuration);
194: return clone;
195: }
196:
197: @Override
198: void mergeChanges(Field field, Object target, Object originalValue, Object cloneValue) {
199: assert originalValue == null || originalValue instanceof Collection;
200: assert cloneValue instanceof Collection;
201:
202: Collection<Object> clone = (Collection<Object>) cloneValue;
203: if (clone instanceof IndirectCollection) {
204: clone = ((IndirectCollection<Collection<Object>>) clone).unwrap();
205: }
206: Collection<Object> orig;
207: if (clone == Collections.emptyList() || clone == Collections.emptySet()) {
208: orig = createDefaultCollection(clone.getClass());
209: } else {
210: final Optional<Collection<?>> origOpt = createNewInstance(clone.getClass(), clone.size());
211: orig = (Collection<Object>) origOpt.orElse(createDefaultCollection(clone.getClass()));
212: }
213: EntityPropertiesUtils.setFieldValue(field, target, orig);
214:
215: if (clone.isEmpty()) {
216: return;
217: }
218: for (Object cl : clone) {
219: orig.add(uow.contains(cl) ? builder.getOriginal(cl) : cl);
220: }
221: final Types types = field.getAnnotation(Types.class);
222: if (types != null) {
223: MetamodelUtils.checkForModuleSignatureExtension(orig, builder.getMetamodel());
224: }
225: }
226:
227: private static Collection<Object> createDefaultCollection(Class<?> cls) {
228: return CollectionFactory.createDefaultCollection(CollectionType.fromClass(cls));
229: }
230:
231: @Override
232: boolean populatesAttributes() {
233: return true;
234: }
235: }